﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
using System.Globalization;
using System.Runtime.InteropServices;
using System.IO;
using System.Drawing;

namespace VMExplorer.MemoryObjects
{
	partial class HeapEntriesTab : UserControl
  {
    #region Private fields

    private HeapObject _mo;

    #endregion
    #region Construction

    public HeapEntriesTab (HeapObject mo)
		{
			InitializeComponent ();
      _mo = mo;
      listView1.VirtualListSize = mo.Entries.Count;
			foreach (ColumnHeader ch in listView1.Columns)
				ch.Width = -2;
    }

    private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
    {
      HeapEntry he = _mo.Entries[e.ItemIndex];

		  e.Item = new ListViewItem (new string [] { 
			  he.Base.ToString(), 
			  he.ChunkSize.ToString(),
			  he.Data.ToString(),
			  he.DataSize.ToString(),
			  he.Type.ToString()
		  });

		  e.Item.Tag = he;

      if (he.Type == HeapEntry.RegionType.Free || he.Type == HeapEntry.RegionType.Region || he.Type == HeapEntry.RegionType.Uncommited)
      {
        e.Item.ForeColor = Color.LightGray;
      }
    }

    #endregion
    #region Dump heap entry

    private int _bytesPerRow = 1;
		private byte[] _buffer = null;

		private static bool CharIsPrintable (char c)
		{
			UnicodeCategory unicodeCategory = char.GetUnicodeCategory (c);
			if (((unicodeCategory == UnicodeCategory.Control) && (unicodeCategory != UnicodeCategory.Format)) && ((unicodeCategory != UnicodeCategory.LineSeparator) && (unicodeCategory != UnicodeCategory.ParagraphSeparator)))
			{
				return (unicodeCategory == UnicodeCategory.OtherNotAssigned);
			}
			return true;
		}

		private void textBox1_SizeChanged (object sender, EventArgs e)
		{
			int charWidth = 1;
			using (Graphics g = textBox1.CreateGraphics ())
			{
				Size size = g.MeasureString ("oooo | hh hh hh hh | aaaa", textBox1.Font).ToSize ();
				charWidth = size.Width / 25;
			}

			_bytesPerRow = ((textBox1.ClientSize.Width / charWidth - 1) - 9) / 4;
			FormatDump ();
		}


		private void FormatDump ()
		{
			int dataSize = _buffer==null ? 0 : _buffer.Length;
			if (dataSize==0)
			{
				textBox1.Text = string.Empty;
				return;
			}
		
			// oooooooo | hh hh hh hh ... hh hh | aaaa ... aa   --   13 + 4n
			StringBuilder sb = new StringBuilder (4 * dataSize);
			StringBuilder asc = new StringBuilder (_bytesPerRow);
			for (int i = 0; i < dataSize; i++)
			{
				if (i % _bytesPerRow == 0)
				{
					sb.AppendFormat ("{0:X4} | ", i);
				}

				sb.AppendFormat ("{0:X2} ", _buffer [i]);

				char c = Convert.ToChar (_buffer [i]);

				if (CharIsPrintable (c))
				{
					asc.Append (c);
				}
				else
				{
					asc.Append ('.');
				}

				if (i % _bytesPerRow == _bytesPerRow - 1)
				{
					sb.Append ("| ");
					sb.Append (asc.ToString ());
					sb.AppendLine ();
					asc.Remove (0,_bytesPerRow);
				}
			}

			// Last line, fill with spaces
			if ((dataSize - 1) % _bytesPerRow != (_bytesPerRow - 1))
			{
				for (int i = 0; i < (_bytesPerRow - 1) - (dataSize - 1) % _bytesPerRow; i++)
					sb.Append ("   ");

				sb.Append ("| ");
				sb.Append (asc.ToString ());
				sb.AppendLine ();
				asc.Remove (0, asc.Length);
			}

			textBox1.Text = sb.ToString ();
		}

		private void listView1_SelectedIndexChanged (object sender, EventArgs e)
		{
			_buffer = null;
      if (listView1.SelectedIndices.Count > 0)
			{
        HeapEntry he = listView1.Items[listView1.SelectedIndices[0]].Tag as HeapEntry;
				if (he==null || he is HeapEntryUncommitted)
				{
					return;
				}

				MemoryAddress data = he.Data;
				int dataSize = he.DataSize;

				// read memory into byte buffer
				IntPtr lpBuffer = Marshal.AllocHGlobal (dataSize);
				if (lpBuffer == IntPtr.Zero) return;

				_buffer = new byte [dataSize];

				try
				{
          if (RemoteReader.ReadMemory(_mo.ProcessHandle, data, lpBuffer, (uint)dataSize))
					{
						unsafe
						{
							UnmanagedMemoryStream ums = new UnmanagedMemoryStream ((byte*)lpBuffer.ToPointer (), dataSize, dataSize, FileAccess.Read);
							ums.Read (_buffer, 0, dataSize);
						}
					}
					else
					{
						_buffer = null;
						return;
					}
				}
				catch
				{
					_buffer = null;
					return;
				}
				finally
				{
					Marshal.FreeHGlobal (lpBuffer);
				}
			}
			FormatDump ();
		}

		#endregion
	}
}
